package com.cloudfun.campusshare.util.tencent.wechat;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.google.common.base.Joiner;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.time.format.DateTimeFormatter;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Created by Huqin on 2020/4/20
 */
@Slf4j
public class WeChatUtil {

    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 对象转map
     *
     * @param bean
     * @return
     */
    public static SortedMap<String, Object> req2Map(Object bean) {
        return req2Map(bean, false);
    }

    /**
     * 对象转map
     *
     * @param bean
     * @param ignoreNullVal
     * @return
     */
    public static SortedMap<String, Object> req2Map(Object bean, boolean ignoreNullVal) {
        Class<?> reqClass = bean.getClass();
        TreeMap<String, Object> fieldMap = new TreeMap<>();
        req2Map(bean, reqClass, fieldMap, ignoreNullVal);
        return fieldMap;
    }

    @SneakyThrows
    private static void req2Map(Object bean, Class<?> clazz, TreeMap<String, Object> fieldMap, boolean ignoreNullVal) {
        if (clazz.equals(Object.class)) {
            return;
        }
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            JacksonXmlProperty annotation = declaredField.getAnnotation(JacksonXmlProperty.class);
            if (null == annotation) {
                continue;
            }
            String name = annotation.localName();
            if (!declaredField.isAccessible()) {
                declaredField.setAccessible(true);
            }
            Object val = declaredField.get(bean);
            if (ignoreNullVal && ObjectUtil.isNull(val)) {
                continue;
            }
            if (val.getClass().isEnum()) {
                Enum e = (Enum) val;
                fieldMap.put(name, e.name());
            } else {
                fieldMap.put(name, val);
            }
        }
        req2Map(bean, clazz.getSuperclass(), fieldMap, ignoreNullVal);
    }

    /**
     * 签名
     *
     * @param sortedMap
     * @param apiKey
     * @return
     */
    public static String sign(SortedMap<String, Object> sortedMap, String apiKey) {
        sortedMap.remove("sign");
        String stringBuilder = Joiner.on("&").withKeyValueSeparator("=").join(sortedMap) +
                "&key=" + apiKey;
        return SecureUtil.md5(stringBuilder).toUpperCase();
    }

    /**
     * 验证消息是否是微信发出的合法消息
     * 签名规则：https://pay.weixin.qq.com/wiki/doc/api/pap.php?chapter=4_3
     *
     * @param sortedMap
     * @param apiKey    设置的密钥
     * @return 验证结果
     */
    public static boolean verifySign(SortedMap<String, Object> sortedMap, String apiKey) {
        // 微信端返回的合法签名
        String actualSign = (String) sortedMap.remove("sign");
        // 计算出来的签名
        String expectedSign = sign(sortedMap, apiKey);

        boolean valid = expectedSign.equalsIgnoreCase(actualSign);
        if (!valid) {
            log.error("微信验签失败，待签名数据：{}，期望签名：{}，实际签名：{}", sortedMap, expectedSign, actualSign);
        }
        return valid;
    }

    /**
     * 随机生成16位字符串.
     */
    public static String genRandomStr() {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 16; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    /**
     * 加密数据解密算法
     * https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html#%E5%8A%A0%E5%AF%86%E6%95%B0%E6%8D%AE%E8%A7%A3%E5%AF%86%E7%AE%97%E6%B3%95
     *
     * @param sessionKey
     * @param encryptedData
     * @param iv
     * @return
     */
    public static String decrypt(String sessionKey, String encryptedData, String iv) {
        log.debug("sessionKey: {}, encryptedData: {}, iv: {}", sessionKey, encryptedData, iv);
        byte[] sessionKeyBy = Base64.decode(sessionKey);
        byte[] encryptedDataBy = Base64.decode(encryptedData);
        byte[] ivBy = Base64.decode(iv);
        byte[] dec = Pkcs7Encoder.decrypt(encryptedDataBy, sessionKeyBy, ivBy);
        String res = new String(dec);
        log.debug("sessionKey: {}, encryptedData: {}, iv: {}, res: {}", sessionKey, encryptedData, iv, res);
        return res;
    }

}
